/**
 * Copyright (C) 2021 - 2031 O-Cubes Co., Ltd.
 */

/****************************************************************
 *  @file    dw_timer.c
 *  @brief   Designware timer driver
 *  @version v1.0
 *  @date    03. Apr. 2023
 ****************************************************************/

#include "error.h"
#include "dw_timer.h"

/** check expressions used in DesignWare TIMER driver implementation */
#define DW_TIMER_CHECK_EXP(EXPR, ERROR_CODE)                CHECK_EXP(EXPR, ercd, ERROR_CODE, error_exit)

#ifndef DISABLE_DEVICE_OBJECT_VALID_CHECK
/** valid check of uart info object */
#define VALID_CHK_TIMER_INFO_OBJECT(timerinfo_obj_ptr) {                       \
	DW_TIMER_CHECK_EXP((timerinfo_obj_ptr) != NULL, E_OBJ);                \
	DW_TIMER_CHECK_EXP(((timerinfo_obj_ptr)->timer_ctrl) != NULL, E_OBJ); \
}
#endif

static inline uint32_t dw_timer_int_read_status(DW_TIMER_CTRL_PTR port)
{
	return port->regs->TIMERS_INT_STATUS;
}

static inline void dw_timer_usr_mode_enable(DW_TIMER_CTRL_PTR port)
{
	port->regs->CTRL[port->id].CONTROL_REG |= 0x02;
}

static inline void dw_timer_usr_mode_disable(DW_TIMER_CTRL_PTR port)
{
	port->regs->CTRL[port->id].CONTROL_REG &= (~0x02);
}

static inline void dw_timer_timer_enable(DW_TIMER_CTRL_PTR port)
{
	port->regs->CTRL[port->id].CONTROL_REG |= 0x01;
}

static inline void dw_timer_timer_disable(DW_TIMER_CTRL_PTR port)
{
	port->regs->CTRL[port->id].CONTROL_REG &= (~0x01);
}

static inline void dw_timer_pwm_enable(DW_TIMER_CTRL_PTR port)
{
	port->regs->CTRL[port->id].CONTROL_REG |= 0x08;
}

static inline void dw_timer_pwm_disable(DW_TIMER_CTRL_PTR port)
{
	port->regs->CTRL[port->id].CONTROL_REG &= (~0x08);
}

static inline void dw_timer_pwm_dutycycle_enable(DW_TIMER_CTRL_PTR port)
{
	port->regs->CTRL[port->id].CONTROL_REG |= 0x10;
}

static inline void dw_timer_pwm_dutycycle_disable(DW_TIMER_CTRL_PTR port)
{
	port->regs->CTRL[port->id].CONTROL_REG &= (~0x10);
}

static inline void dw_timer_count_set(DW_TIMER_CTRL_PTR port, uint32_t val)
{
	port->regs->CTRL[port->id].LOAD_COUNT = val;
}

static inline uint32_t dw_timer_count_get(DW_TIMER_CTRL_PTR port)
{
	return port->regs->CTRL[port->id].LOAD_COUNT;
}

static inline void dw_timer_count2_set(DW_TIMER_CTRL_PTR port, uint32_t val)
{
	port->regs->LOAD_COUNT2[port->id] = val;
}

static inline uint32_t dw_timer_count2_get(DW_TIMER_CTRL_PTR port)
{
	return port->regs->LOAD_COUNT2[port->id];
}

static void dw_timer_int_clear(DW_TIMER_CTRL_PTR port)
{
	int32_t reg_val;

	reg_val = port->regs->CTRL[port->id].EOI;
	reg_val = reg_val;
}

/* interface for DEV_TIMER */
/** Open designware timer device */
int32_t dw_timer_open(DEV_TIMER *timer_dev)
{
	int32_t ercd = E_OK;
	DEV_TIMER_INFO_PTR port_info_ptr = &(timer_dev->timer_info);

	/* START ERROR CHECK */
	VALID_CHK_TIMER_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_TIMER_CTRL_PTR port = (DW_TIMER_CTRL_PTR)(port_info_ptr->timer_ctrl);

	port_info_ptr->opn_cnt++;

	if (port_info_ptr->opn_cnt > 1) { /* opened before */
		return E_OPNED;
	}

	dw_timer_timer_disable(port);
	dw_timer_pwm_disable(port);
	irq_disable(port->intno);
	irq_attach(port->intno, port->int_handler, NULL);

error_exit:
	return ercd;
}

/** Close designware timer device */
int32_t dw_timer_close(DEV_TIMER *timer_dev)
{
	int32_t ercd = E_OK;
	DEV_TIMER_INFO_PTR port_info_ptr = &(timer_dev->timer_info);

	/* START ERROR CHECK */
	VALID_CHK_TIMER_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_TIMER_CTRL_PTR port = (DW_TIMER_CTRL_PTR)(port_info_ptr->timer_ctrl);

	DW_TIMER_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_OK);

	port_info_ptr->opn_cnt--;

	if (port_info_ptr->opn_cnt == 0) {

		dw_timer_timer_disable(port);
		dw_timer_pwm_disable(port);
		irq_disable(port->intno);
	} else {
		ercd = E_OPNED;
	}

error_exit:
	return ercd;
}

/** Read designware timer device value */
int32_t dw_timer_read(DEV_TIMER *timer_dev, uint32_t *mode, uint32_t *freq, uint32_t *dc)
{
	int32_t ercd = E_OK;
	DEV_TIMER_INFO_PTR port_info_ptr = &(timer_dev->timer_info);

	/* START ERROR CHECK */
	VALID_CHK_TIMER_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_TIMER_CTRL_PTR port = (DW_TIMER_CTRL_PTR)(port_info_ptr->timer_ctrl);

	DW_TIMER_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);

	int32_t count_low = dw_timer_count_get(port);
	int32_t count_high = dw_timer_count2_get(port);

	*mode = port->mode;

	if ((*mode == DEV_TIMER_MODE_ONESHOT_TMR) ||
	        (*mode == DEV_TIMER_MODE_PERIODIC_TMR)) {
		*dc = 100;

		if (count_low != 0) {
			*freq = port->clock / (count_low * 2);
		} else {
			*freq = 0;
		}
	} else if (*mode == DEV_TIMER_MODE_PWM) {
		if (count_low == 0 && count_high == 0) {
			*dc = 0;
			*freq = 0;
		} else {
			*dc = (count_high * 100) / (count_high + count_low);
			*freq = port->clock / (count_high + count_low);
		}
	} else {
		*dc = 0;
		*freq = 0;
	}

error_exit:
	return ercd;
}

/** Write designware timer device value */
int32_t dw_timer_write(DEV_TIMER *timer_dev, uint32_t mode, uint32_t freq, uint32_t dc)
{
	int32_t ercd = E_OK;
	int32_t count, count_high;
	DEV_TIMER_INFO_PTR port_info_ptr = &(timer_dev->timer_info);

	/* START ERROR CHECK */
	VALID_CHK_TIMER_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_TIMER_CTRL_PTR port = (DW_TIMER_CTRL_PTR)(port_info_ptr->timer_ctrl);

	DW_TIMER_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);
	DW_TIMER_CHECK_EXP(dc <= 100, E_PAR);

	if ((mode == DEV_TIMER_MODE_ONESHOT_TMR) ||
	        (mode == DEV_TIMER_MODE_PERIODIC_TMR)) {
		DW_TIMER_CHECK_EXP(freq > 0, E_PAR);
		port->mode = mode;

		count = port->clock / freq;
		dw_timer_count_set(port, count / 2);
		dw_timer_count2_set(port, 0);

		dw_timer_timer_enable(port);
		dw_timer_pwm_disable(port);
		irq_enable(port->intno);
	} else if (mode == DEV_TIMER_MODE_PWM) {
		DW_TIMER_CHECK_EXP(freq > 0, E_PAR);
		port->mode = mode;

		count = port->clock / freq;
		count_high = (count * dc) / 100;
		dw_timer_count_set(port, count - count_high);
		dw_timer_count2_set(port, count_high);
		dw_timer_timer_enable(port);
		dw_timer_pwm_enable(port);
		irq_disable(port->intno);
	} else if (mode == DEV_TIMER_MODE_CLOSE) {
		port->mode = mode;
		dw_timer_count_set(port, 0);
		dw_timer_count2_set(port, 0);
		dw_timer_timer_disable(port);
		dw_timer_pwm_disable(port);
		irq_disable(port->intno);
	}

error_exit:
	return ercd;
}

/** Control designware timer device */
int32_t dw_timer_control(DEV_TIMER *timer_dev, uint32_t ctrl_cmd, void *par)
{
	int32_t ercd = E_OK;
	DEV_TIMER_INFO_PTR port_info_ptr = &(timer_dev->timer_info);

	/* START ERROR CHECK */
	VALID_CHK_TIMER_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_TIMER_CTRL_PTR port = (DW_TIMER_CTRL_PTR)(port_info_ptr->timer_ctrl);

	DW_TIMER_CHECK_EXP(port_info_ptr->opn_cnt > 0, E_CLSED);

	DEV_TIMER_CFG *cfg_ptr;

	switch (ctrl_cmd) {
		case TIMER_CMD_SET_CFG:
			cfg_ptr = (DEV_TIMER_CFG *) par;

			if (cfg_ptr->mode == DEV_TIMER_MODE_ONESHOT_TMR
			        || cfg_ptr->mode == DEV_TIMER_MODE_PERIODIC_TMR) {
				port->mode = cfg_ptr->mode;
				port->int_ch_handler = cfg_ptr->int_ch_handler;
				dw_timer_count_set(port, cfg_ptr->count_low);
				dw_timer_count2_set(port, cfg_ptr->count_high);
				dw_timer_pwm_disable(port);

				if (cfg_ptr->mode == DEV_TIMER_MODE_ONESHOT_TMR) {
					dw_timer_usr_mode_disable(port);
				} else if (cfg_ptr->mode == DEV_TIMER_MODE_PERIODIC_TMR) {
					dw_timer_usr_mode_enable(port);
				}

				dw_timer_timer_enable(port);
				irq_enable(port->intno);
			}  else if (cfg_ptr->mode == DEV_TIMER_MODE_PWM) {
				if (cfg_ptr->duty_cycle > 100) {
					ercd = E_PAR;
					break;
				}

				port->mode = cfg_ptr->mode;
				dw_timer_pwm_dutycycle_enable(port);
				dw_timer_pwm_enable(port);
				dw_timer_usr_mode_enable(port);
				if ((cfg_ptr->duty_cycle == 0) || (cfg_ptr->duty_cycle == 100)) {
					cfg_ptr->count_low = 100 - cfg_ptr->duty_cycle;
					cfg_ptr->count_high = cfg_ptr->duty_cycle;
				}
				else {
					cfg_ptr->count_low = ((uint64_t)(port->clock) * (100 - cfg_ptr->duty_cycle)) / (cfg_ptr->hz * 100);
					cfg_ptr->count_high = ((uint64_t)(port->clock) * cfg_ptr->duty_cycle) / (cfg_ptr->hz * 100);
				}
				dw_timer_count_set(port, cfg_ptr->count_low);
				dw_timer_count2_set(port, cfg_ptr->count_high);
				dw_timer_timer_enable(port);
				//irq_enable(port->intno);
			} else if (cfg_ptr->mode == DEV_TIMER_MODE_CLOSE) {
				port->mode = cfg_ptr->mode;

				if (port->mode == DEV_TIMER_MODE_PWM) {
					dw_timer_pwm_dutycycle_disable(port);
				}

				dw_timer_timer_disable(port);
				dw_timer_usr_mode_disable(port);
				dw_timer_pwm_disable(port);
				irq_disable(port->intno);
			}

			break;

		case TIMER_CMD_GET_CFG:
			cfg_ptr = (DEV_TIMER_CFG *) par;
			cfg_ptr->count_low = dw_timer_count_get(port);
			cfg_ptr->count_high = dw_timer_count2_get(port);
			cfg_ptr->mode = port->mode;
			break;

		case TIMER_CMD_DIS_CH:
			dw_timer_timer_disable(port);
			break;

		case TIMER_CMD_ENA_CH:
			dw_timer_timer_enable(port);
			break;

		case TIMER_CMD_DIS_ISR:
			irq_disable(port->intno);
			break;

		case TIMER_CMD_ENA_ISR:
			irq_enable(port->intno);
			break;

		case TIMER_CMD_SET_ISR:
			DW_TIMER_CHECK_EXP((par != NULL), E_PAR);
			port->int_ch_handler = (DEV_TIMER_HANDLER)par;
			break;

		case TIMER_CMD_GET_ISR:
			DW_TIMER_CHECK_EXP((par != NULL), E_PAR);
			par = (void *)(port->int_ch_handler);
			break;

		default:
			ercd = E_NOSPT;
			break;
	}

error_exit:
	return ercd;
}

/** designware timer interrupt process */
int32_t dw_timer_isr_handler(DEV_TIMER *timer_dev, void *ptr)
{
	int32_t ercd = E_OK;
	uint32_t timer_bit_isr_state;
	DEV_TIMER_INFO_PTR port_info_ptr = &(timer_dev->timer_info);

	/* START ERROR CHECK */
	VALID_CHK_TIMER_INFO_OBJECT(port_info_ptr);
	/* END OF ERROR CHECK */

	DW_TIMER_CTRL_PTR port = (DW_TIMER_CTRL_PTR)(port_info_ptr->timer_ctrl);

	timer_bit_isr_state = dw_timer_int_read_status(port);

	if (timer_bit_isr_state & (1 << port->id)) {
		if (port->mode == DEV_TIMER_MODE_ONESHOT_TMR) {
			port->mode = DEV_TIMER_MODE_CLOSE;
			dw_timer_timer_disable(port);
			dw_timer_pwm_disable(port);
			irq_disable(port->intno);
		}

		if (port->int_ch_handler) {
			port->int_ch_handler(CONV2VOID(&port->id));
		}
	}

	dw_timer_int_clear(port); /** clear this bit interrupt */

	ptr = ptr;

error_exit:
	return ercd;
}
